package com.gitee.melin.bee.core.conf;

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.gitee.melin.bee.util.ThreadUtils;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Maps;
import org.apache.commons.configuration2.PropertiesConfiguration;
import org.apache.commons.configuration2.PropertiesConfigurationLayout;
import org.apache.commons.configuration2.convert.DefaultListDelimiterHandler;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.core.Ordered;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;

import java.io.StringReader;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.*;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * Created by admin on 2017/6/26.
 */
public class BeeConfigClient implements ApplicationContextAware, Ordered {

    private static final Logger LOGGER = LoggerFactory.getLogger(BeeConfigClient.class);

    private static final AtomicBoolean init = new AtomicBoolean(false);

    private ScheduledExecutorService executorService;

    private static BeeConfigClient beeConfigClient;

    private String lastConfigText = "";

    private JdbcTemplate jdbcTemplate;

    private String configSql;

    private Class<? extends BeeConf> confClass;

    private String keyPrefix = "";

    private PropertiesConfiguration configuration = new PropertiesConfiguration();

    private ObjectMapper mapper = new ObjectMapper();

    private Map<String, String> defaultMapStringValue = Collections.unmodifiableMap(new HashMap<>());

    private Map<String, Integer> defaultMapIntegerValue = Collections.unmodifiableMap(new HashMap<>());

    private Map<String, Boolean> defaultMapBooleanValue = Collections.unmodifiableMap(new HashMap<>());

    public BeeConfigClient(JdbcTemplate jdbcTemplate, String configSql, Class<? extends BeeConf> confClass) {
        this(jdbcTemplate, configSql, confClass, "");
    }

    public BeeConfigClient(JdbcTemplate jdbcTemplate, String configSql, Class<? extends BeeConf> confClass, String keyPrefix) {
        if (!init.compareAndSet(false, true)) {
            throw new RuntimeException("ConfigClient 只能初始化一次");
        }

        if (StringUtils.isNotBlank(keyPrefix) && !StringUtils.endsWith(keyPrefix, ".")) {
            throw new IllegalArgumentException("keyPrefix 不为空时，必须以英文点结束");
        }

        if (StringUtils.isNotBlank(keyPrefix)) {
            checkConfigKeyValid(confClass, keyPrefix);
        }

        this.jdbcTemplate = jdbcTemplate;
        this.configSql = configSql;
        this.confClass = confClass;
        this.keyPrefix = keyPrefix;

        // 允许使用未带引号的字段名
        mapper.configure(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, true);
        // 允许使用单引号
        mapper.configure(JsonParser.Feature.ALLOW_SINGLE_QUOTES, true);

        iniConfig();
    }

    @Override
    public int getOrder() {
        return HIGHEST_PRECEDENCE;
    }

    private void checkConfigKeyValid(Class<? extends BeeConf> confClass, String keyPrefix) {
        try {
            Field[] fields = confClass.getDeclaredFields();
            for (Field field : fields) {
                if (Modifier.isStatic(field.getModifiers())) {
                    Object value = field.get(null);
                    if (value instanceof ConfigEntry) {
                        ConfigEntry entry = (ConfigEntry) value;
                        String key = entry.getKey();
                        if (!StringUtils.startsWith(entry.getKey(), keyPrefix)) {
                            throw new IllegalArgumentException(key + " 前缀不为: " + keyPrefix);
                        }

                        if (StringUtils.isBlank(entry.getVersion())) {
                            throw new IllegalArgumentException("version 不能为空");
                        }
                    }
                }
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public static BeeConfigClient getInstance() {
        if (beeConfigClient == null) {
            throw new RuntimeException("ConfigClient 还没有被初始化");
        }
        return beeConfigClient;
    }

    private void loadConfig(String configText) {
        PropertiesConfigurationLayout layout = new PropertiesConfigurationLayout();
        try {
            configuration.setListDelimiterHandler(new DefaultListDelimiterHandler(','));
            layout.load(configuration, new StringReader(configText));
        } catch (Exception e) {
            LOGGER.error(e.getMessage());
        }
    }

    private void iniConfig() {
        try {
            if (StringUtils.isNotBlank(lastConfigText)) {
                loadConfig(lastConfigText);
            } else {
                lastConfigText = jdbcTemplate.queryForObject(configSql, String.class);
                loadConfig(lastConfigText);
                LOGGER.info("完成config加载");
            }

            executorService = ThreadUtils.newDaemonSingleThreadScheduledExecutor("refresh-config");
            executorService.scheduleWithFixedDelay(() -> {
                try {
                    String newConfigText = jdbcTemplate.queryForObject(configSql, String.class);

                    if (newConfigText != null && !StringUtils.equals(lastConfigText, newConfigText)) {
                        LOGGER.info("更新配置：\n {}", newConfigText);

                        Properties properties = new Properties();
                        properties.load(new StringReader(newConfigText));

                        Enumeration<?> enumeration = properties.propertyNames();
                        while (enumeration.hasMoreElements()) {
                            String key = (String) enumeration.nextElement();
                            configuration.setProperty(key, properties.getProperty(key));
                        }

                        lastConfigText = newConfigText;
                    }
                } catch (Exception e) {
                    LOGGER.error(e.getMessage(), e);
                }
            }, 0, 5, TimeUnit.SECONDS);

        } catch (EmptyResultDataAccessException e) {
            LOGGER.error("配置信息不存在，请初始化配置");
        } catch (Exception e) {
            LOGGER.error(e.getMessage(), e);
        }
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        beeConfigClient = applicationContext.getBean(BeeConfigClient.class);
    }

    //------------------------------------------------------------------------------

    public String getString(ConfigEntry<String> entry) {
        String value = configuration.getString(entry.getKey(), entry.getDefaultValue());
        if (entry.getValidValues() != null && !entry.getValidValues().contains(value)) {
            throw new IllegalArgumentException(
                    "The value of " + entry.getKey() + " should be one of "
                            + StringUtils.join(entry.getValidValues(), ", ") + ", but was " + value);
        }
        return value;
    }

    public String[] getStringArray(ConfigEntry<StringArray> entry) {
        String[] values = configuration.getStringArray(entry.getKey());
        if (values == null || values.length == 0) {
            return entry.getDefaultValue().getValues();
        } else {
            return values;
        }
    }

    public Integer getInteger(ConfigEntry<Integer> entry) {
        Integer value = configuration.getInteger(entry.getKey(), entry.getDefaultValue());
        if (entry.getValidValues() != null && !entry.getValidValues().contains(value)) {
            throw new IllegalArgumentException(
                    "The value of " + entry.getKey() + " should be one of "
                            + StringUtils.join(entry.getValidValues(), ", ") + ", but was " + value);
        }
        return value;
    }

    public Long getLong(ConfigEntry<Long> entry) {
        Long value = configuration.getLong(entry.getKey(), entry.getDefaultValue());
        if (entry.getValidValues() != null && !entry.getValidValues().contains(value)) {
            throw new IllegalArgumentException(
                    "The value of " + entry.getKey() + " should be one of "
                            + StringUtils.join(entry.getValidValues(), ", ") + ", but was " + value);
        }
        return value;
    }

    public Float getFloat(ConfigEntry<Float> entry) {
        Float value = configuration.getFloat(entry.getKey(), entry.getDefaultValue());
        if (entry.getValidValues() != null && !entry.getValidValues().contains(value)) {
            throw new IllegalArgumentException(
                    "The value of " + entry.getKey() + " should be one of "
                            + StringUtils.join(entry.getValidValues(), ", ") + ", but was " + value);
        }
        return value;
    }

    public Double getDouble(ConfigEntry<Double> entry) {
        Double value = configuration.getDouble(entry.getKey(), entry.getDefaultValue());
        if (entry.getValidValues() != null && !entry.getValidValues().contains(value)) {
            throw new IllegalArgumentException(
                    "The value of " + entry.getKey() + " should be one of "
                            + StringUtils.join(entry.getValidValues(), ", ") + ", but was " + value);
        }
        return value;
    }

    public Boolean getBoolean(ConfigEntry<Boolean> entry) {
        Boolean value = configuration.getBoolean(entry.getKey(), entry.getDefaultValue());
        if (entry.getValidValues() != null && !entry.getValidValues().contains(value)) {
            throw new IllegalArgumentException(
                    "The value of " + entry.getKey() + " should be one of "
                            + StringUtils.join(entry.getValidValues(), ", ") + ", but was " + value);
        }
        return value;
    }

    public Map<String, String> getMapString(ConfigEntry<MapStringValue> entry) {
        String[] array = configuration.getStringArray(entry.getKey());
        String json = "{}";
        if (array.length > 0) {
            json = StringUtils.join(array, ", ");
        }
        try {
            Map<String, String> map = mapper.readValue(json, Map.class);

            if (map == null || map.size() == 0) {
                return entry.getDefaultValue().getValue();
            } else {
                return map;
            }
        } catch (Exception e) {
            String msg = "解析" + entry.getKey() + "值失败, 失败原因: " + e.getMessage() + "\n配置值: " + json;
            LOGGER.error(msg);

            return defaultMapStringValue;
        }
    }

    public Map<String, Integer> getMapInteger(ConfigEntry<MapIntegerValue> entry) {
        String[] array = configuration.getStringArray(entry.getKey());
        String json = "{}";
        if (array.length > 0) {
            json = StringUtils.join(array, ", ");
        }
        try {
            Map<String, Integer> map = mapper.readValue(json, Map.class);

            if (map == null || map.size() == 0) {
                return entry.getDefaultValue().getValue();
            } else {
                return map;
            }
        } catch (Exception e) {
            String msg = "解析" + entry.getKey() + "值失败, 失败原因: " + e.getMessage() + "\n配置值: " + json;
            LOGGER.error(msg);

            return defaultMapIntegerValue;
        }
    }

    public Map<String, Boolean> getMapBoolean(ConfigEntry<MapBooleanValue> entry) {
        String[] array = configuration.getStringArray(entry.getKey());
        String json = "{}";
        if (array.length > 0) {
            json = StringUtils.join(array, ", ");
        }
        try {
            Map<String, Boolean> map = mapper.readValue(json, Map.class);

            if (map == null || map.size() == 0) {
                return entry.getDefaultValue().getValue();
            } else {
                return map;
            }
        } catch (Exception e) {
            String msg = "解析" + entry.getKey() + "值失败, 失败原因: " + e.getMessage() + "\n配置值: " + json;
            LOGGER.error(msg);

            return defaultMapBooleanValue;
        }
    }

    public Map<String, String> getAllConfig() {
        Iterator<String> keys = configuration.getKeys();
        Map<String, String> result = Maps.newHashMap();
        while (keys.hasNext()) {
            String key = keys.next();
            String[] array = configuration.getStringArray(key);
            if (array.length == 1) {
                result.put(key, array[0]);
            } else {
                result.put(key, StringUtils.join(array, ","));
            }
        }

        return result;
    }

    public List<String> getKeys() {
        return ImmutableList.copyOf(configuration.getKeys());
    }

    public Map<String, String> getAllConfig(String prefix) {
        Iterator<String> keys = configuration.getKeys(prefix);
        Map<String, String> result = Maps.newHashMap();
        while (keys.hasNext()) {
            String key = keys.next();
            String[] array = configuration.getStringArray(key);
            if (array.length == 1) {
                result.put(key, array[0]);
            } else {
                result.put(key, StringUtils.join(array, ","));
            }
        }

        return result;
    }

    public List<String> getKeys(String prefix) {
        return ImmutableList.copyOf(configuration.getKeys(prefix));
    }
}
